Skip to content

docs(ui): a contributed navigation entry's order sorts it among the target app's own entries - #33472

Open
objectstack-fleet[bot] wants to merge 1 commit into
mainfrom
claude/project-thread-dn0u43
Open

objectstack-fleet[bot] wants to merge 1 commit into
mainfrom
claude/project-thread-dn0u43

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

domain:spec#1 · os-project-manager · session_01FEkQ5pRMxnBBFzFztXjepS

Fixes #33447

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:content/docs/ui/apps.mdx 的「向其他应用贡献导航」一节补一段话、示例加一个 order: 30;导航条目表的 order 行补半句「其他包贡献的条目也在同一层参与排序」。另在 objectql 现有的导航贡献测试文件里加 3 条钉子,钉住合并时贡献条目的 order 原样保留。
  • 为什么改:文档只讲了 group 和 priority,下游以为模块的条目只能排在应用自有条目之后。实际上合并原样保留 order,控制台按 order 排每一层,所以 order: 25 会排在应用的 20 和 30 之间,前提是应用给自己的条目编了号。这是 app nav: a per-audience landing (and group placement) cannot name an entry contributed by a module package — so an industry module can never be a role's landing page or reorder past the app's own groups #33234 裁定 C(不改)后留给文档的缺口。
  • 风险与回滚:不改任何运行代码,不改 spec;只有一页文档和一个测试文件。回滚即还原本 PR。
  • 先看:content/docs/ui/apps.mdx L863 起的新段落;packages/objectql/src/registry-nav-contribution-group-semantics.test.ts L509 起的新 describe。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/registry-nav-contribution-group-semantics.test.ts,预期 13 条全绿(原 10 条 + 新 3 条)。
  • 需要你做的:无,席位落地。

What changes

  • content/docs/ui/apps.mdx, the navigationContributions section (L863). One paragraph: the merge appends a contribution's items to the end of their group (or the top level) as written, order included; the console then sorts each level by order (the top level, each group's children, the mobile tab bar), so order: 25 is drawn between the app's own 20 and 30. No order sorts as 0; equal order keeps the merged sequence, so priority decides only between contributions whose items have the same order; the target app has to number its own entries, otherwise a contribution stays after them. The example's nav_webhooks item gains order: 30 (L847).
  • Same file, the Navigation items table's order row (L297). One clause: entries contributed by another package sort in the same level, linking to the section.
  • packages/objectql/src/registry-nav-contribution-group-semantics.test.ts, new top-level describe (L509), beside the existing group/priority propositions in the same harness file: a host app that numbers its entries plus contributor modules, installed as one artifact through the manifest service, read with registry.getApp.
    • In a group: sales_group children read [nav_accounts, 10], [nav_contacts, 30], [nav_cpq, 20]: appended, not sorted in, order untouched.
    • At the top level: [nav_home, 10], [sales_group, 20], [nav_reports, 30], [nav_forecast, 25].
    • An item with no order has no order key after the fold, in a group or at the top level.

What the code does, read on origin/main 19cc8976 before writing: SchemaRegistry.applyNavContributions (packages/objectql/src/registry.ts L11003) pushes c.items into the group's children (L11024) or the top level (L11034 relocation, L11037 no group) with no map or sort; the console's one comparator is byNavOrder in ui/packages/layout/src/navOrder.ts ((a.order ?? 0) - (b.order ?? 0), stable sort), used at NavigationRenderer.tsx L2113 (top level) and L1797 (a group's children) and AppSchemaRenderer.tsx L327 (mobile tab bar). That comparator is pinned by ui/packages/layout/src/__tests__/AppSchemaRenderer.mobileTabOrder-11395.test.tsx; this PR does not re-test it.

No source line moves, nothing in packages/spec changes, no new key, no before / after, no landing on a contributed entry (#33234 ruled C, record 6884379643).

Tests

Ablation (head 5357d88d, scripts/ablation-replace.mjs on packages/objectql/src/registry.ts, each anchor 1 → 0, replacement 0 → 1; the test imports ./registry.js and ./plugin.js from src/, no dist in the path):

Three mutations of the fold, each red; restored blob 85ef55a712f7 == HEAD, 13 passed
A1 drop order in a group: group.children.push(...c.items) -> push(...c.items.map(({ order: _o, ...rest }) => rest))
   blob 85ef55a712f7 -> 76658cf717f5
   x in a group: the item lands after the group's own children with its `order` untouched
A2 rewrite order at the top level: nav.push(...c.items) in the no-group branch -> push(...c.items.map((i) => ({ ...i, order: 0 })))
   blob 85ef55a712f7 -> bd8b6e20c4d1
   x at the top level: the item lands after the app's own entries with its `order` untouched
   x an item with no `order` gets none from the fold, in a group or at the top level
A3 sort the group level in the fold: group.children = [...group.children, ...c.items].sort(by order)
   blob 85ef55a712f7 -> edf1b4046ff5
   x in a group: the item lands after the group's own children with its `order` untouched
   Tests 1 failed | 12 passed (13)
each leg: ablation-replace: ok restored: blob == HEAD (85ef55a712f7) and `git diff HEAD` is empty
restored run: Tests 13 passed (13)

Acceptance notes

  • Docs gate on the example (measured). content/docs/ui/apps.mdx is in check:skill-examples' docs root, but only fences under an os:check marker are type-checked; the page has 4 marked fences and the navigationContributions example fence is not one of them, so no snippet type-check runs on the edited block. The new in-page link #contributing-navigation-to-another-app is read by check:doc-anchors (exit 0).
  • No changeset, skip-changeset. @objectstack/objectql ships files: [dist, README.md, CHANGELOG.md]; a *.test.ts is not in its build, and content/docs/** is in no package. No published byte moves.
  • Ablation A3 versus the console. A fold that sorted would also draw the right menu, so A3 going red is the pin doing what the card asked (the fold never re-sorts); the drawn order is the console's job and is pinned there.

Verification

On head 5357d88d (branch claude/project-thread-dn0u43, merge base origin/main 19cc8976), each exit code captured before any pipe:

  • pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 (the package's local project, under the verify lock): exit 0, Test Files 596 passed (596), Tests 13005 passed (13005).
  • pnpm --filter @objectstack/objectql typecheck (tsc --noEmit plus check:test-typecheck over tsconfig.test.json, which compiles the test layer): exit 0, "39 file(s) / 233 error(s) / 64 pinned signature(s) held", no new signature.
  • eslint --no-inline-config on the test file: exit 0.
  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 81 families): 80 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package's dist/), NOT MEASURED, CI runs it. Three families first answered exit 3 on prerequisites and were rerun green after clearing them: check-plugin-teardown-shape.mjs --self-test (fixture commit fetched), check:skill-examples (client and client-react built; "344 prose examples type-check across 3 surface(s)"), check:lean-entry-closure (objectql built). --ran: "81 derived famil(ies) accounted for — 80 run, 1 NOT-MEASURED".
  • dispatch-gates --tier: no path-derived mandate. 135 changed lines, under the 3000 threshold. No governed surface.
  • CI families are read from this PR's check-runs, not re-run here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5pRMxnBBFzFztXjepS

…arget app's own entries

The navigationContributions section named only group and priority. The
fold (SchemaRegistry.applyNavContributions) appends a contribution's items
as authored, order included, and the console sorts every level by order
afterwards (ui/packages/layout/src/navOrder.ts byNavOrder: the top level,
each group's children, the mobile tab bar; pinned by
ui/packages/layout/src/__tests__/AppSchemaRenderer.mobileTabOrder-11395.test.tsx).
Say so, with the consequences: no order sorts as 0, ties keep the fold's
sequence so priority only breaks ties, and the host must number its own
entries for a contribution to land between them.

Pin the objectql half beside the group-semantics cases: the fold keeps a
contributed item's order (and appends it after the host's entries) in a
group and at the top level, and writes no order onto an item that has none.

Claude-Session: https://claude.ai/code/session_01FEkQ5pRMxnBBFzFztXjepS
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 14, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b5f2f516b88353360d1fc7c3c6bf60db01cbff9e → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-project-manager · session_01FEkQ5mswnWm181XgYcCUag

ACCEPT · PR #33472 (#33447) · head 5357d88d17184274d2fd339a30e7d7b37821fcc7 · 2026-11-14T23:05Z

席位核对

  • 形态: 2 files, +133/−2 (read from the PR's file list, not the report): content/docs/ui/apps.mdx (one paragraph in the navigationContributions section, order: 30 in its example, one clause in the navigation items order row) and three cases in a new describe of packages/objectql/src/registry-nav-contribution-group-semantics.test.ts. skip-changeset is right: neither file ships in a package. No governed path, no packages/spec path (record-recognisers: TIER_DEFAULT, no face), under 3000 lines: Tier S, the seat's own ACCEPT.
  • 前提实测: the seat read the docs diff against the code on main: applyNavContributions (packages/objectql/src/registry.ts) pushes each contribution's items as authored, order included; ui/packages/layout/src/navOrder.ts#byNavOrder sorts each level by order, missing as 0, stable on ties. Every sentence of the new paragraph matches, including "priority decides only between contributions whose items have the same order" and "the target app must number its own entries". The pin's three ablations (drop order in a group, rewrite it at the top level, sort inside the fold) each went red, per the report.
  • 门禁 check-runs: latest attempt on this head 58 runs: 38 success, 20 skipped, 0 failed. Attempt 1's Test Core (7/8) failed on one @objectstack/plugin-approvals case (action-page-member.integration.test.ts, test timed out at 5000 ms), a package this diff does not touch; its one re-run of the failed jobs is green. The same case timed out once on PR fix(lint): the flow and hook publish doors stop requiring a null guard on the current row's created_at and updated_at in an ordering comparison #33434's head too; if it reds a third PR the seat files it.
  • 契约复审: not required: no hits path (no spec, no governed surface). Seat ACCEPT at TIER_DEFAULT.
  • 接受的偏差: none. Clause-②: no / Metadata-protocol: none. check:dual-build-cjs-loads not measured locally (needs every dist); CI's build and type-check runs answer it.
  • 范围外去向: none. The ruling behind the card is app nav: a per-audience landing (and group placement) cannot name an entry contributed by a module package — so an industry module can never be a role's landing page or reorder past the app's own groups #33234's C (record 6884379643); this PR adds no key and no capability.
  • 落地条款: ready + auto-merge now under the maintainer's standing order 「按照 pm-dispatch 规则复核过的都授权」 (2026-10-25T11:35Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (2026-11-07T16:59Z). K = 10 commits on main past this head's affected-set base b5f2f516 (< 15, no merge of main needed).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 14, 2026 23:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 43657223386 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (7/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-approvals:test:  FAIL  src/action-page-member.integration.test.ts > [#22578] handleActionPage — the ADR-0043 action page from a Request > GET renders the confirm page for a live to
      ↳ 失败原因: @objectstack/plugin-approvals:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 23 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 43659594732 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (7/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-approvals:test:  FAIL  src/action-page-member.integration.test.ts > [#22578] handleActionPage — the ADR-0043 action page from a Request > GET renders the confirm page for a live to
      ↳ 失败原因: @objectstack/plugin-approvals:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 23 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: the navigationContributions section does not say a contributed entry's order sorts it among the target app's own entries

1 participant